module ChessData

  # A PositionDefinition is a collection of criteria (matchers) which define 
  # a valid type of position.
  #
  # e.g. some kind of Rook endings.
  #
  #   database.select do 
  #     at_least 5, "P"
  #     exactly 1, "R"
  #     at_most 4, "p"
  #     exactly 1, "r"
  #   end
  #
  class PositionDefinition

    # The constructor evaluates the provided _block_, to set up the user's 
    # board criteria. It then sets up some default criteria for the king's and 
    # pieces not covered by the block.
    def initialize(&block)
      @matchers = []
      instance_eval(&block)

      # fill in a default matcher for any not present
      ["K", "k"].each do |piece|
        unless @matchers.any? {|matcher| matcher.piece == piece}
          exactly 1, piece
        end
      end
      ["P", "p", "N", "n", "B", "b", "R", "r", "Q", "q"].each do |piece|
        unless @matchers.any? {|matcher| matcher.piece == piece}
          exactly 0, piece
        end
      end
    end

    # Checks that all the criteria are matched by the given board.
    def check board
      @matchers.all? do |matcher|
        matcher.check board
      end
    end

    private

    # following methods used in block to constructor as DSL
    # for a pattern definition

    def exactly n, *pieces
      pieces.each do |piece|
        @matchers << ExactCount.new(n, piece)
      end
    end

    def at_least n, *pieces
      pieces.each do |piece|
        @matchers << AtLeastCount.new(n, piece)
      end
    end

    def at_most n, *pieces
      pieces.each do |piece|
        @matchers << AtMostCount.new(n, piece)
      end
    end
  end

  # parent class for the different types of count comparators
  class Counter 
    attr_reader :piece

    # Each counter has a target _piece_ and reference number _n_.
    def initialize n, piece
      @n = n
      @piece = piece
    end
  end

  # The ExactCount class checks that there are exactly _n_ of the 
  # named piece on the board.
  class ExactCount < Counter 
    # Returns true if board contains _n_ of _piece_
    def check board
      board.count(@piece) == @n
    end
  end

  # The AtLeastCount class checks that there are at least _n_ of the 
  # named piece on the board.
  class AtLeastCount < Counter 
    # Returns true if board contains at least _n_ of _piece_
    def check board
      board.count(@piece) >= @n
    end
  end

  # The AtMostCount class checks that there are at most _n_ of the 
  # named piece on the board.
  class AtMostCount < Counter
    # Returns true if board contains at most _n_ of _piece_
    def check board
      board.count(@piece) <= @n
    end
  end
end

